# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

cmake_minimum_required(VERSION 3.8 FATAL_ERROR)

set(CMAKE_MODULE_PATH
  # For in-fbsource builds on mac
  "${CMAKE_CURRENT_SOURCE_DIR}/../opensource/fbcode_builder/CMake"
  # For shipit-transformed builds
  "${CMAKE_CURRENT_SOURCE_DIR}/build/fbcode_builder/CMake"
  ${CMAKE_MODULE_PATH})

set(CMAKE_CXX_STANDARD 17)

# Tell CMake to also look in the directories where getdeps.py installs
# our third-party dependencies.
list(APPEND CMAKE_PREFIX_PATH "${CMAKE_CURRENT_SOURCE_DIR}/external/install")

option(BUILD_SHARED_LIBS
  "If enabled, build libraries as a shared library.  \
  This is generally discouraged, since we do not commit to having \
  a stable ABI."
  OFF
)
# Mark BUILD_SHARED_LIBS as an "advanced" option, since enabling it
# is generally discouraged.
mark_as_advanced(BUILD_SHARED_LIBS)

option(USE_SYS_PYTHON
  "If enabled, prefers to use the system installed python vs the user \
  installed python. Flipping this switch will change which python is used \
  during packaging and should be set to maximize portability."
  ON
)

enable_testing()
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
include_directories(${CMAKE_CURRENT_BINARY_DIR})
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/external/install/include")

option(ENABLE_EDEN_SUPPORT "If enabled, add support for the Eden \
  virtual filesystem.  That requires fbthrift."
  ON)

# Determine whether we are the git repo produced by shipit, a staging
# area produced by shipit in the FB internal CI, or whether
# we are building in the source monorepo.
# For the FB internal CI flavor running shipit, CMAKE_CURRENT_SOURCE_DIR
# will have a value like "..../shipit_projects/watchman".
if (IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/.git" OR
    "${CMAKE_CURRENT_SOURCE_DIR}" MATCHES "shipit_projects")
  set(IS_SHIPPED_IT TRUE)
else()
  set(IS_SHIPPED_IT FALSE)
endif()

# If we're building from inside the monorepo, make the local directory
# look like the shipit-transformed source in the git repo.
# On windows we do a dumb recursive copy of the files because we cannot
# guarantee that we'll be successful in setting up a symlink.
# On everything else we set up a simple symlink.
# In theory we can tell cmake to add a non-child subdir and avoid the
# copy/symlink thing, but we'd need to teach various targets how to resolve
# the path and that is rather a lot of work (I spent a couple of hours on this
# before throwing in the towel).
function(maybe_shipit_dir MONOREPO_RELATIVE_PATH)
  get_filename_component(base "${MONOREPO_RELATIVE_PATH}" NAME)
  if (NOT IS_SHIPPED_IT AND
      NOT IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${base})
    if (WIN32)
      file(COPY
        "${CMAKE_CURRENT_SOURCE_DIR}/${MONOREPO_RELATIVE_PATH}"
        DESTINATION ${CMAKE_CURRENT_SOURCE_DIR})
    else()
      execute_process(COMMAND
        ln -s ${MONOREPO_RELATIVE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/${base})
    endif()
  endif()
endfunction()

if (ENABLE_EDEN_SUPPORT)
  # We use shipit to mirror in these locations from the monorepo
  maybe_shipit_dir("../eden")
endif()

# A nonsensical version that doesn't correspond to any manually
# released version.
set(PACKAGE_VERSION "0.0.0")
set(WATCHMAN_VERSION_OVERRIDE "" CACHE STRING "Use this version code for \
    Watchman version instead of the default computed from the repo")
set(WATCHMAN_BUILDINFO_OVERRIDE "" CACHE STRING "Use this version code for \
    Watchman build info instead of the default (nothing)")

if (WATCHMAN_VERSION_OVERRIDE)
  set(PACKAGE_VERSION "${WATCHMAN_VERSION_OVERRIDE}")
elseif(DEFINED ENV{WATCHMAN_VERSION_OVERRIDE})
  set(PACKAGE_VERSION "$ENV{WATCHMAN_VERSION_OVERRIDE}")
elseif(DEFINED ENV{FBSOURCE_DATE})
  # If set, we expect FBSOURCE_DATE to have the form "20200324.113140"
  set(PACKAGE_VERSION "$ENV{FBSOURCE_DATE}.0")
  set(BUILD_INFO "$ENV{FBSOURCE_HASH}")
else()
  find_program(GIT git)
  if(GIT)
    execute_process(
      COMMAND "${GIT}" "show" "-s" "--format=%H;%cd" "--date=format:%Y%m%d.%H%M%S.0"
      WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
      RESULT_VARIABLE git_result
      OUTPUT_VARIABLE git_data
      ERROR_VARIABLE git_err
      OUTPUT_STRIP_TRAILING_WHITESPACE
    )
    if(git_result EQUAL 0)
      list(GET git_data 0 BUILD_INFO)
      list(GET git_data 1 PACKAGE_VERSION)
    endif()
  endif()
endif()
message(STATUS "PACKAGE_VERSION=${PACKAGE_VERSION}, BUILD_INFO=${BUILD_INFO}")

set(PACKAGE_NAME      "watchman")
set(PACKAGE_STRING    "${PACKAGE_NAME} ${PACKAGE_VERSION}")
set(PACKAGE_TARNAME   "${PACKAGE_NAME}-${PACKAGE_VERSION}")
set(PACKAGE_BUGREPORT "https://github.com/facebook/watchman/issues")
project(${PACKAGE_NAME} CXX C)

find_package(GMock MODULE REQUIRED)
include_directories(${GMOCK_INCLUDEDIR} ${LIBGMOCK_INCLUDE_DIR})
include(GoogleTest)
enable_testing()

include(FBThriftCppLibrary)
include(CheckFunctionExists)
include(CheckIncludeFiles)
include(CheckStructHasMember)
include(CheckSymbolExists)
include(RustStaticLibrary)

# configure_file wants us to define a separate file.  I'd rather not
# have boilerplate for the same thing in two difference files, so we
# roll the checks in together with writing out the features to config.h
# ourselves here.
function(config_h LINE)
  file(APPEND "${CMAKE_CURRENT_BINARY_DIR}/config.h.new" "${LINE}\n")
endfunction()

file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/config.h.new" "#pragma once\n")

if(NOT WIN32)
  set(WATCHMAN_STATE_DIR "${CMAKE_INSTALL_PREFIX}/var/run/watchman" CACHE STRING
    "Run-time path of the persistent state directory")
  set(INSTALL_WATCHMAN_STATE_DIR OFF CACHE BOOL
    "Whether WATCHMAN_STATE_DIR should be created by the cmake install
    target.  Disabling this is useful in the case where the CMAKE_INSTALL_PREFIX
    is owned by a non-privileged user but where the WATCHMAN_STATE_DIR requires
    administrative rights to create and set its permissions.")
else()
  set(WATCHMAN_STATE_DIR)
  set(INSTALL_WATCHMAN_STATE_DIR)
endif()

if(WATCHMAN_STATE_DIR AND INSTALL_WATCHMAN_STATE_DIR)
  install(DIRECTORY DESTINATION ${WATCHMAN_STATE_DIR}
    DIRECTORY_PERMISSIONS
    OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_WRITE
    GROUP_EXECUTE WORLD_READ WORLD_WRITE WORLD_EXECUTE SETGID)
endif()

config_h("// Generated by cmake")
if(WIN32)
  config_h("#define WATCHMAN_CONFIG_FILE \
\"C:/ProgramData/facebook/watchman.json\"")
else()
  config_h("#define WATCHMAN_CONFIG_FILE \"/etc/watchman.json\"")
endif()

if(WATCHMAN_STATE_DIR)
  config_h("#define WATCHMAN_STATE_DIR \"${WATCHMAN_STATE_DIR}\"")
endif()
config_h("#define PACKAGE_VERSION \"${PACKAGE_VERSION}\"")

if(BUILD_INFO)
  config_h("#define WATCHMAN_BUILD_INFO \"${BUILD_INFO}\"")
endif ()


# While most of these tests are not strictly needed on windows, it is vital
# that we probe for and find strtoll in order for the jansson build to use
# a 64-bit integer type, otherwise the mtime_us field renders as garbage
# in the integration tests.
foreach(wat_func
    accept4
    backtrace
    backtrace_symbols
    backtrace_symbols_fd
    fdopendir
    getattrlistbulk
    inotify_init
    inotify_init1
    kqueue
    localeconv
    memmem
    mkostemp
    openat
    pipe2
    port_create
    statfs
    strtoll
    sys_siglist
)
  CHECK_FUNCTION_EXISTS(${wat_func} have_${wat_func})
  if (have_${wat_func})
    string(TOUPPER have_${wat_func} sym)
    config_h("#define ${sym} 1")
  endif()
endforeach(wat_func)

foreach(wat_header
    CoreServices/CoreServices.h
    execinfo.h
    fcntl.h
    inttypes.h
    locale.h
    port.h
    sys/event.h
    sys/inotify.h
    sys/mount.h
    sys/param.h
    sys/resource.h
    sys/socket.h
    sys/statfs.h
    sys/statvfs.h
    sys/types.h
    sys/ucred.h
    sys/vfs.h
    valgrind/valgrind.h
)
  CHECK_INCLUDE_FILES(${wat_header} have_${wat_header})
  if (have_${wat_header})
    string(TOUPPER have_${wat_header} sym)
    string(REGEX REPLACE [./] _ sym ${sym})
    config_h("#define ${sym} 1")
  endif()
endforeach(wat_header)

CHECK_STRUCT_HAS_MEMBER(statvfs f_fstypename sys/statvfs.h
  HAVE_STRUCT_STATVFS_F_FSTYPENAME)
if (HAVE_STRUCT_STATVFS_F_BASETYPE)
  config_h("define HAVE_STRUCT_STATVFS_F_FSTYPENAME 1")
endif()

CHECK_STRUCT_HAS_MEMBER(statvfs f_basetype sys/statvfs.h
  HAVE_STRUCT_STATVFS_F_BASETYPE)
if (HAVE_STRUCT_STATVFS_F_BASETYPE)
  config_h("define HAVE_STRUCT_STATVFS_F_BASETYPE 1")
endif()

if(have_fcntl.h)
  CHECK_SYMBOL_EXISTS(O_SYMLINK fcntl.h HAVE_DECL_O_SYMLINK)
  if(HAVE_DECL_O_SYMLINK)
    config_h("#define HAVE_DECL_O_SYMLINK 1")
  endif()
endif()
find_package(PCRE2)
if(PCRE2_FOUND)
  config_h("#define HAVE_PCRE_H 1")
endif()

# Now close out config.h.  We only want to touch the file if the contents are
# different, so do a little dance to figure that out.
if(EXISTS "${CMAKE_CURRENT_BINARY_DIR}/config.h")
  file(MD5 "${CMAKE_CURRENT_BINARY_DIR}/config.h" orig_hash)
  file(MD5 "${CMAKE_CURRENT_BINARY_DIR}/config.h.new" this_hash)
  if(NOT orig_hash STREQUAL this_hash)
    file(RENAME "${CMAKE_CURRENT_BINARY_DIR}/config.h.new"
      "${CMAKE_CURRENT_BINARY_DIR}/config.h")
  endif()
else()
  file(RENAME "${CMAKE_CURRENT_BINARY_DIR}/config.h.new"
    "${CMAKE_CURRENT_BINARY_DIR}/config.h")
endif()

set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)

# This block is for cmake 3.0 which doesn't define the Threads::Threads
# interface section.  Test for that and define it for ourselves.
if(THREADS_FOUND AND NOT TARGET Threads::Threads)
  add_library(Threads::Threads INTERFACE IMPORTED)

  if(THREADS_HAVE_PTHREAD_ARG)
    set_property(TARGET Threads::Threads PROPERTY
      INTERFACE_COMPILE_OPTIONS "-pthread")
  endif()

  if(CMAKE_THREAD_LIBS_INIT)
    set_property(TARGET Threads::Threads PROPERTY
      INTERFACE_LINK_LIBRARIES "${CMAKE_THREAD_LIBS_INIT}")
  endif()
endif()

find_package(OpenSSL)

# This block is for cmake 3.0 which doesn't define the OpenSSL::Crypto
# interface section.  Test for that and define it for ourselves.
if(OPENSSL_FOUND AND NOT TARGET OpenSSL::Crypto)
  add_library(OpenSSL::Crypto UNKNOWN IMPORTED)
    set_target_properties(OpenSSL::Crypto PROPERTIES
      INTERFACE_INCLUDE_DIRECTORIES "${OPENSSL_INCLUDE_DIR}")
    if(EXISTS "${OPENSSL_CRYPTO_LIBRARY}")
      set_target_properties(OpenSSL::Crypto PROPERTIES
        IMPORTED_LINK_INTERFACE_LANGUAGES "C"
        IMPORTED_LOCATION "${OPENSSL_CRYPTO_LIBRARY}")
    endif()
endif()

find_package(Gflags REQUIRED)
include_directories(SYSTEM ${GFLAGS_INCLUDE_DIR})

find_package(Glog REQUIRED)
add_compile_definitions(GLOG_NO_ABBREVIATED_SEVERITIES)

# We indirectly depend on boost.  This logic needs to match
# the same criteria used in thrift, which wants static libs
# on windows.
if(MSVC)
  set(Boost_USE_STATIC_LIBS ON) #Force static lib in msvc
endif(MSVC)
find_package(
  Boost 1.54.0 REQUIRED #1.54.0 or greater
  COMPONENTS
    context
    thread
)

# We indirectly depend on libevent.  Folly pulls in linkage to
# event.lib, but on my system it does so as simply "event.lib"
# and that fails linking.  Let's probe for the library and force
# in the library directory for the linker. :-/
find_package(LibEvent REQUIRED)
get_filename_component(LIBEVENT_LIBDIR "${LIBEVENT_LIB}" DIRECTORY)
link_directories(${LIBEVENT_LIBDIR})
find_package(edencommon CONFIG REQUIRED)
find_package(fmt CONFIG REQUIRED)
find_package(folly CONFIG REQUIRED)

if (ENABLE_EDEN_SUPPORT)
  find_package(fizz CONFIG REQUIRED)
  find_package(wangle CONFIG REQUIRED)
  find_package(FBThrift CONFIG REQUIRED)
  find_package(fb303 CONFIG REQUIRED)
  find_package(cpptoml CONFIG REQUIRED)
  include_directories(${FB303_INCLUDE_DIR})
endif()
if(DEFINED ENV{NODE_BIN})
  set(NODE $ENV{NODE_BIN})
else()
  find_program(NODE node)
endif()
if(DEFINED ENV{YARN_PATH})
  set(YARN $ENV{YARN_PATH})
else()
  find_program(YARN yarn)
endif()


if(NOT WIN32)
  # Sometimes the environment has a Python installation earlier in the
  # PATH that is not suitable for building extensions. Prefer the
  # primary system installation.
  if(USE_SYS_PYTHON)
    set(Python3_ROOT_DIR /usr/bin)
  else()
    set(Python3_ROOT_DIR /usr/local/bin)
  endif()
endif()

find_package(Python3 COMPONENTS Interpreter Development)

if(NOT Python3_Interpreter_FOUND)
  unset(Python3_ROOT_DIR)
  find_package(Python3 COMPONENTS Interpreter Development)
endif()


if(Python3_Development_FOUND)
  set(PYOUT "${CMAKE_CURRENT_BINARY_DIR}/build/pytimestamp")
  set(SETUP_PY "${CMAKE_CURRENT_SOURCE_DIR}/watchman/python/setup.py")
  file(GLOB PYWATCHMAN_PY_SRCS "watchman/python/pywatchman/*.py")
  add_custom_command(
    COMMENT "Building pywatchman"
    OUTPUT ${PYOUT}
    DEPENDS ${PYWATCHMAN_PY_SRCS} "watchman/python/pywatchman/bser.c"
    COMMAND ${CMAKE_COMMAND} -E env
      CMAKE_CURRENT_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR}
      CMAKE_CURRENT_BINARY_DIR=${CMAKE_CURRENT_BINARY_DIR}
      ${Python3_EXECUTABLE} ${SETUP_PY} build
    COMMAND ${CMAKE_COMMAND} -E touch ${PYOUT}
  )
  add_custom_target(pybuild ALL DEPENDS ${PYOUT})
  install(CODE "
    execute_process(COMMAND
      ${CMAKE_COMMAND} -E env
        CMAKE_CURRENT_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR}
        ${Python3_EXECUTABLE} ${SETUP_PY} install
        --root $ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}
      RESULT_VARIABLE STATUS)
    if (NOT STATUS STREQUAL 0)
      message(FATAL_ERROR \"pywatchman install failed\")
    endif()
  ")
  include(FBPythonBinary)
  add_subdirectory(watchman/python)
  add_subdirectory(watchman/integration)
endif()

if(Python3_Interpreter_FOUND AND NODE AND YARN)
  add_subdirectory(watchman/node/bser)
endif()

if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
  # Check target architecture
  if (NOT CMAKE_SIZEOF_VOID_P EQUAL 8)
    message(FATAL_ERROR "watchman requires a 64bit target architecture.")
  endif()
  add_definitions(-D_CRT_SECURE_NO_WARNINGS=1)
  include_directories(${CMAKE_CURRENT_SOURCE_DIR}/watchman/winbuild)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zi /Zo /MP /Oi /EHsc /GL- /wd4250")
  set(CMAKE_SHARED_LINKER_FLAGS
    "${CMAKE_SHARED_LINKER_FLAGS} /DEBUG /OPT:NOREF")
  set(CMAKE_EXE_LINKER_FLAGS
    "${CMAKE_EXE_LINKER_FLAGS} /DEBUG /OPT:NOREF")
  set(CMAKE_MODULE_LINKER_FLAGS
    "${CMAKE_MODULE_LINKER_FLAGS} /DEBUG /OPT:NOREF")
  set(CMAKE_STATIC_LIBRARY_FLAGS
    "${CMAKE_STATIC_LIBRARY_FLAGS} /DEBUG /OPT:NOREF")
else()
  set(CMAKE_CXX_FLAGS_COMMON "-g -Wall -Wextra -std=gnu++17")
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_COMMON}")  # for cmake 3.0
  set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_COMMON}")
  set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_COMMON} -O3")
endif()

add_library(third_party_deps INTERFACE)
target_link_libraries(third_party_deps INTERFACE
  Folly::folly
  glog::glog
  gflags
  ${Boost_LIBRARIES}
  fmt::fmt
  edencommon::utils
)
target_include_directories(third_party_deps INTERFACE
  ${FOLLY_INCLUDE_DIR}
  ${GLOG_INCLUDE_DIR}
  ${GFLAGS_INCLUDE_DIR}
  ${Boost_INCLUDE_DIRS}
)
if (ENABLE_EDEN_SUPPORT)
  target_link_libraries(
    third_party_deps
    INTERFACE
    ${YARPL_LIBRARIES}
    FBThrift::thriftcpp2
    cpptoml
  )
endif()
if(PCRE2_FOUND)
  target_link_libraries(third_party_deps INTERFACE ${PCRE2_LIBRARY})
  target_include_directories(third_party_deps INTERFACE ${PCRE2_INCLUDE_DIR})
  target_compile_definitions(third_party_deps INTERFACE ${PCRE2_DEFINES})
  if (WIN32)
    # The pcre headers assume that the library is a dll by default
    # but our preferred build environment only builds them as
    # static, so be sure to ask for static pcre linkage
    target_compile_definitions(third_party_deps INTERFACE PCRE2_STATIC)
  endif()
endif()
target_link_libraries(third_party_deps INTERFACE Threads::Threads)
if(TARGET OpenSSL::Crypto)
  target_link_libraries(third_party_deps INTERFACE OpenSSL::Crypto)
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
  target_link_libraries(third_party_deps INTERFACE "-framework CoreServices")
elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows")
  target_link_libraries(third_party_deps INTERFACE
    advapi32.lib
    dbghelp.lib
    shlwapi.lib
  )
endif()

add_library(wildmatch STATIC
  watchman/thirdparty/wildmatch/wildmatch.c
  watchman/thirdparty/wildmatch/wildmatch.h
)
add_library(log STATIC
  watchman/PubSub.cpp
  watchman/LogConfig.cpp
  watchman/Logging.cpp
  watchman/portability/Backtrace.cpp
)
target_link_libraries(log third_party_deps)
add_library(hash STATIC watchman/hash.cpp)
target_link_libraries(hash third_party_deps)
add_library(err STATIC watchman/Poison.cpp watchman/root/warnerr.cpp)
target_link_libraries(err third_party_deps)
add_library(jansson_utf STATIC watchman/thirdparty/jansson/utf.cpp)

add_library(string STATIC watchman/string.cpp)
target_link_libraries(string jansson_utf hash third_party_deps)

add_library(jansson STATIC
watchman/thirdparty/jansson/dump.cpp
watchman/thirdparty/jansson/error.cpp
watchman/thirdparty/jansson/load.cpp
watchman/thirdparty/jansson/strconv.cpp
watchman/thirdparty/jansson/value.cpp
)
target_link_libraries(jansson string third_party_deps)

list(APPEND testsupport_sources
watchman/ChildProcess.cpp
watchman/fs/FileDescriptor.cpp
watchman/fs/FileInformation.cpp
watchman/fs/FSDetect.cpp
watchman/FlagMap.cpp
watchman/IgnoreSet.cpp
watchman/PendingCollection.cpp
watchman/fs/Pipe.cpp
watchman/fs/WindowsTime.cpp
watchman/ThreadPool.cpp
watchman/WatchmanConfig.cpp
watchman/bser.cpp
watchman/fs/UnixDirHandle.cpp
watchman/fs/WinDirHandle.cpp
watchman/stream.cpp
watchman/stream_unix.cpp
watchman/stream_win.cpp
watchman/portability/PosixSpawn.cpp
watchman/portability/WinError.cpp
watchman/root/dir.cpp
watchman/root/file.cpp
)

add_library(testsupport STATIC ${testsupport_sources})
target_link_libraries(testsupport log string jansson third_party_deps)

if (ENABLE_EDEN_SUPPORT)
  add_fbthrift_cpp_library(
    eden_config_thrift
    eden/fs/config/eden_config.thrift
  )
  add_fbthrift_cpp_library(
    eden_service_thrift
    eden/fs/service/eden.thrift
    SERVICES
      EdenService
    DEPENDS
      eden_config_thrift
      fb303::fb303_thrift_cpp
  )
  add_fbthrift_cpp_library(
    streamingeden_thrift
    eden/fs/service/streamingeden.thrift
    SERVICES
      StreamingEdenService
    DEPENDS
      eden_service_thrift
      fb303::fb303_thrift_cpp
  )
endif()

list(APPEND watchman_sources
watchman/ChildProcess.cpp
watchman/Client.cpp
watchman/Clock.cpp
watchman/Command.cpp
watchman/CommandRegistry.cpp
watchman/Connect.cpp
watchman/ContentHash.cpp
watchman/CookieSync.cpp
watchman/Errors.cpp
watchman/fs/FileDescriptor.cpp
watchman/fs/FileInformation.cpp
watchman/fs/FileSystem.cpp
watchman/FlagMap.cpp
watchman/fs/FSDetect.cpp
watchman/GroupLookup.cpp
watchman/IgnoreSet.cpp
watchman/InMemoryView.cpp
watchman/Options.cpp
watchman/PDU.cpp
watchman/PendingCollection.cpp
watchman/PerfSample.cpp
watchman/fs/ParallelWalk.cpp
watchman/fs/Pipe.cpp
watchman/ProcessLock.cpp
# PubSub.cpp  (in liblog)
watchman/QueryableView.cpp
watchman/SanityCheck.cpp
watchman/Shutdown.cpp
watchman/SignalHandler.cpp
watchman/SymlinkTargets.cpp
watchman/ThreadPool.cpp
watchman/TriggerCommand.cpp
watchman/fs/UnixDirHandle.cpp
watchman/fs/WindowsTime.cpp
watchman/UserDir.cpp
watchman/WatchmanConfig.cpp
watchman/fs/WinDirHandle.cpp
watchman/bser.cpp
# hash.cpp (in libhash)
watchman/launchd.cpp
watchman/listener-user.cpp
watchman/listener.cpp
watchman/main.cpp
watchman/sockname.cpp
watchman/state.cpp
watchman/stream.cpp
watchman/stream_unix.cpp
watchman/stream_stdout.cpp
watchman/stream_win.cpp
# string.cpp (in libstring)
watchman/portability/PosixSpawn.cpp
watchman/portability/WinError.cpp
watchman/query/FileResult.cpp
watchman/query/LocalFileResult.cpp
watchman/query/GlobTree.cpp
watchman/query/QueryContext.cpp
watchman/query/Query.cpp
watchman/query/QueryResult.cpp
watchman/query/TermRegistry.cpp
watchman/query/base.cpp
watchman/query/dirname.cpp
watchman/query/empty.cpp
watchman/query/eval.cpp
watchman/query/fieldlist.cpp
watchman/query/glob.cpp
watchman/query/intcompare.cpp
watchman/query/match.cpp
watchman/query/name.cpp
watchman/query/parse.cpp
watchman/query/pcre.cpp
watchman/query/since.cpp
watchman/query/suffix.cpp
watchman/query/type.cpp
watchman/cmds/debug.cpp
watchman/cmds/find.cpp
# cmds/heapprof.cpp
watchman/cmds/info.cpp
watchman/cmds/log.cpp
watchman/cmds/query.cpp
watchman/cmds/since.cpp
watchman/cmds/state.cpp
watchman/cmds/subscribe.cpp
watchman/cmds/trigger.cpp
watchman/cmds/watch.cpp
watchman/root/ageout.cpp
watchman/root/dir.cpp
watchman/root/file.cpp
watchman/root/init.cpp
watchman/root/iothread.cpp
watchman/root/notifythread.cpp
watchman/# root/poison.cpp (in liberr)
watchman/root/reap.cpp
watchman/root/resolve.cpp
watchman/root/sync.cpp
watchman/root/threading.cpp
# root/warnerr.cpp (in liberr)
watchman/root/watchlist.cpp
watchman/saved_state/LocalSavedStateInterface.cpp
watchman/saved_state/SavedStateFactory.cpp
watchman/saved_state/SavedStateInterface.cpp
watchman/scm/Git.cpp
watchman/scm/Mercurial.cpp
watchman/scm/SCM.cpp
watchman/thirdparty/getopt/GetOpt.cpp
watchman/watcher/Watcher.cpp
watchman/watcher/WatcherRegistry.cpp
watchman/watcher/fsevents.cpp
watchman/watcher/inotify.cpp
watchman/watcher/kqueue.cpp
watchman/watcher/portfs.cpp
watchman/watcher/kqueue_and_fsevents.cpp
watchman/watcher/win32.cpp
)

if (ENABLE_EDEN_SUPPORT)
  # We currently only support talking to eden on posix systems
  list(APPEND watchman_sources watchman/watcher/eden.cpp)
endif()

add_executable(watchman ${watchman_sources})
set_target_properties(watchman PROPERTIES
  RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
)
target_link_libraries(
  watchman
  log
  hash
  string
  err
  jansson
  wildmatch
  third_party_deps
)

if (WIN32)
  add_subdirectory(watchman/thirdparty/deelevate_binding)
  target_link_libraries(
    watchman
    libdeelevate
  )
  install_rust_executable(deelevate)
endif()

if (ENABLE_EDEN_SUPPORT)
  target_link_libraries(
    watchman
    streamingeden_thrift
  )
endif()

install(TARGETS watchman RUNTIME DESTINATION bin)

add_subdirectory(watchman/cli)

set(tests)
# Helper function to define a unit test executable
function(t_test NAME)
  add_executable(${NAME}.t ${ARGN})
  target_link_libraries(
    ${NAME}.t
    testsupport wildmatch third_party_deps
    ${LIBGMOCK_LIBRARIES}
  )
  target_compile_definitions(${NAME}.t
    PUBLIC WATCHMAN_TEST_SRC_DIR=\"${CMAKE_CURRENT_SOURCE_DIR}\")
  gtest_discover_tests(${NAME}.t)
  list(APPEND tests ${NAME}.t)
endfunction()

# The `check` target runs the unit tests
add_custom_target(check
  DEPENDS ${tests}
  COMMAND ${CMAKE_CTEST_COMMAND})

if(Python3_Interpreter_FOUND)
  if (WIN32)
    add_executable(susres watchman/winbuild/susres.cpp)
    add_custom_target(make_susres ALL DEPENDS susres)
  endif()

  # The `integration` target runs the unit tests and integration tests
  add_custom_target(integration
    DEPENDS pybuild check
    COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/runtests.py
      --watchman-path ${CMAKE_CURRENT_BINARY_DIR}/watchman
      --pybuild-dir ${CMAKE_CURRENT_BINARY_DIR}/watchman/python
      WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
endif()

t_test(art watchman/test/ArtTest.cpp)
t_test(bser watchman/test/BserTest.cpp)
t_test(cache watchman/test/CacheTest.cpp)
t_test(childproc watchman/test/ChildProcTest.cpp)
t_test(fsdetect watchman/test/FSDetectTest.cpp)
t_test(ignore watchman/test/BserTest.cpp)
# Linking this test needs the targets graph to be cleaned up.
#t_test(inmemoryview watchman/test/InMemoryViewTest.cpp)
t_test(log watchman/test/LogTest.cpp)
t_test(maputil watchman/test/MapUtilTest.cpp)
t_test(pendingcollection watchman/test/PendingCollectionTest.cpp)
# Linking this test needs the targets graph to be cleaned up.
#t_test(perfsample watchman/test/PerfSampleTest.cpp)
t_test(result watchman/test/ResultTest.cpp)
t_test(ringbuffer watchman/test/RingBufferTest.cpp)
t_test(string watchman/test/StringTest.cpp)
t_test(wildmatch watchman/test/WildmatchTest.cpp)
